package routers

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"strconv"

	"gitee.com/law-api/log"
)

//VerifyRequest receive verify-data request from accounts
type VerifyRequest struct {
	Way    int    `json:"way"` //verify way: 1-fileID,2-Sha256,3-sm3,4-all
	TxHash string `json:"tran_hash"`
	FileID string `json:"file_id,omitempty"`
	Sha256 string `json:"sha256,omitempty"`
	Sm3    string `json:"sm3,omitempty"`
	Md5    string `json:"md5,omitempty"`
}

//VerifyResponse return status about verify request
type VerifyResponse struct {
	Code int           `json:"code"`
	Msg  string        `json:"msg"`
	Data []*VerifyData `json:"data"`
}

func newVerifyResopnse(err error) *VerifyResponse {
	code := checkVerifyErrCode(err)
	msg := "成功"
	if code != 0 {
		msg = err.Error()
	}

	return &VerifyResponse{
		Code: code,
		Msg:  msg,
		Data: make([]*VerifyData, 0),
	}
}

//VerifyData return data about verify request
type VerifyData struct {
	BlockNumber string `json:"block_height"`
	BlockHash   string `json:"block_hash"`
	TxHash      string `json:"tran_hash"`
	Timestamp   string `json:"block_timestamp"`
	FileID      string `json:"file_id"`
	Sha256      string `json:"sha256"`
	Sm3         string `json:"sm3"`
	Md5         string `json:"md5"`
}

//VerifyHandler manager how to call contract from picoChain
type VerifyHandler struct {
	chain Chain
}

//NewVerifyHandler is a handler to deal verify request
func NewVerifyHandler(chain Chain) *VerifyHandler {
	return &VerifyHandler{chain: chain}
}

//String show the main function of chain hanlder
func (vh *VerifyHandler) String() string {
	return "verify data in blockchain"
}

//ServeHTTP check and handle verify request
func (vh *VerifyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" {

		w.Header().Add("Content-Type", "application/json;charset=utf-8")

		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			log.Warn("verifyHandler", "err", err)
			w.Write(encRespon(newVerifyResopnse(ErrHTTPRequest)))
			return
		}
		defer r.Body.Close()

		//parse request
		req := new(VerifyRequest)
		if err = json.Unmarshal(b, req); err != nil {
			log.Warn("verifyHandler", "err", err)
			w.Write(encRespon(newVerifyResopnse(ErrHTTPRequest)))
			return
		}

		//check tran_hash
		if req.TxHash == "" {
			log.Warn("empty tran_hash")
			w.Write(encRespon(newVerifyResopnse(ErrTxHash)))
			return
		}

		//get staus from picoChain
		res := vh.handleVerifyReq(req)
		w.Write(encRespon(res))

	} else {
		log.Warn("unknown request", "method", r.Method)
		w.Write(encRespon(newVerifyResopnse(ErrHTTPRequest)))
	}
}

//handleVerifyReq select a vaild way to handle request.
//		way: 1, index: "fileid";
//		way: 2, index: "filehash";
//		way: 3, index: "sm3";
//		way: 4, index: "fileid" | "filehash" | "sm3" | "md5"
//when way is other status, only answer where transaction storage
func (vh *VerifyHandler) handleVerifyReq(req *VerifyRequest) *VerifyResponse {
	//when we succeed,fill data in this response
	res := newVerifyResopnse(nil)
	resData := &VerifyData{}

	//find tx replace first
	chData, err := vh.chain.GetChainStatus(req.TxHash)
	if err != nil {
		return newVerifyResopnse(err)
	}
	resData.BlockNumber = chData.BlockNumber()
	resData.BlockHash = chData.BlockHash()
	resData.TxHash = chData.TxHash()
	resData.Timestamp = chData.Timestamp()

	//check validity in this request,if we need get file status
	if idx, mode := findIndex(req); idx != "" {
		//find origin data
		number, _ := strconv.ParseUint(resData.BlockNumber, 10, 64)
		fData, err := vh.chain.GetProofData(idx, mode, req.TxHash, number)
		if err != nil {
			log.Warn("verify err", "idx", idx, "txhash", req.TxHash, "err", err)
			return newVerifyResopnse(err)
		}
		resData.FileID = fData.FileID()
		resData.Sha256 = fData.FileHash()
		resData.Sm3 = fData.Sm3()
		resData.Md5 = fData.Md5()
	}
	res.Data = append(res.Data, resData)

	return res
}

//checkReqWay select the method for reading contract
func findIndex(req *VerifyRequest) (string, int) {
	if req.FileID != "" && (req.Way == 1 || req.Way == 4) {
		return req.FileID, 1
	}
	if req.Sha256 != "" && (req.Way == 2 || req.Way == 4) {
		return req.Sha256, 2
	}
	if req.Sm3 != "" && (req.Way == 3 || req.Way == 4) {
		return req.Sm3, 3
	}
	if req.Md5 != "" && (req.Way == 4) {
		return req.Md5, 4
	}
	return "", -1
}
